axonpush 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. axonpush-0.1.0/.gitignore +13 -0
  2. axonpush-0.1.0/.python-version +1 -0
  3. axonpush-0.1.0/LICENSE +21 -0
  4. axonpush-0.1.0/PKG-INFO +204 -0
  5. axonpush-0.1.0/README.md +161 -0
  6. axonpush-0.1.0/pyproject.toml +64 -0
  7. axonpush-0.1.0/src/axonpush/__init__.py +48 -0
  8. axonpush-0.1.0/src/axonpush/_auth.py +24 -0
  9. axonpush-0.1.0/src/axonpush/_http.py +121 -0
  10. axonpush-0.1.0/src/axonpush/_tracing.py +52 -0
  11. axonpush-0.1.0/src/axonpush/_version.py +1 -0
  12. axonpush-0.1.0/src/axonpush/client.py +102 -0
  13. axonpush-0.1.0/src/axonpush/exceptions.py +37 -0
  14. axonpush-0.1.0/src/axonpush/integrations/__init__.py +7 -0
  15. axonpush-0.1.0/src/axonpush/integrations/anthropic.py +184 -0
  16. axonpush-0.1.0/src/axonpush/integrations/crewai.py +132 -0
  17. axonpush-0.1.0/src/axonpush/integrations/langchain.py +250 -0
  18. axonpush-0.1.0/src/axonpush/integrations/openai_agents.py +133 -0
  19. axonpush-0.1.0/src/axonpush/models/__init__.py +26 -0
  20. axonpush-0.1.0/src/axonpush/models/apps.py +19 -0
  21. axonpush-0.1.0/src/axonpush/models/channels.py +20 -0
  22. axonpush-0.1.0/src/axonpush/models/common.py +8 -0
  23. axonpush-0.1.0/src/axonpush/models/events.py +49 -0
  24. axonpush-0.1.0/src/axonpush/models/traces.py +33 -0
  25. axonpush-0.1.0/src/axonpush/models/webhooks.py +51 -0
  26. axonpush-0.1.0/src/axonpush/py.typed +0 -0
  27. axonpush-0.1.0/src/axonpush/realtime/__init__.py +3 -0
  28. axonpush-0.1.0/src/axonpush/realtime/sse.py +86 -0
  29. axonpush-0.1.0/src/axonpush/realtime/websocket.py +210 -0
  30. axonpush-0.1.0/src/axonpush/resources/__init__.py +18 -0
  31. axonpush-0.1.0/src/axonpush/resources/apps.py +69 -0
  32. axonpush-0.1.0/src/axonpush/resources/channels.py +107 -0
  33. axonpush-0.1.0/src/axonpush/resources/events.py +109 -0
  34. axonpush-0.1.0/src/axonpush/resources/traces.py +52 -0
  35. axonpush-0.1.0/src/axonpush/resources/webhooks.py +96 -0
  36. axonpush-0.1.0/uv.lock +4793 -0
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.py[oc]
3
+ build/
4
+ dist/
5
+ wheels/
6
+ *.egg-info
7
+ .venv
8
+ .env
9
+ data/*.db
10
+ data/resumes/
11
+ rendercv_output/
12
+ migrations/versions/
13
+ .claude/settings.local.json
@@ -0,0 +1 @@
1
+ 3.13
axonpush-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 AxonPush
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,204 @@
1
+ Metadata-Version: 2.4
2
+ Name: axonpush
3
+ Version: 0.1.0
4
+ Summary: Python SDK for AxonPush — real-time event infrastructure for AI agent systems
5
+ Author: AxonPush
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Framework :: Pydantic :: 2
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Typing :: Typed
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: httpx-sse<1.0,>=0.4.0
18
+ Requires-Dist: httpx<1.0,>=0.25.0
19
+ Requires-Dist: pydantic<3.0,>=2.0
20
+ Provides-Extra: all
21
+ Requires-Dist: anthropic>=0.30.0; extra == 'all'
22
+ Requires-Dist: crewai>=0.50.0; extra == 'all'
23
+ Requires-Dist: langchain-core>=0.1.0; extra == 'all'
24
+ Requires-Dist: openai-agents>=0.1.0; extra == 'all'
25
+ Requires-Dist: python-socketio[asyncio-client]<6.0,>=5.10; extra == 'all'
26
+ Provides-Extra: anthropic
27
+ Requires-Dist: anthropic>=0.30.0; extra == 'anthropic'
28
+ Provides-Extra: crewai
29
+ Requires-Dist: crewai>=0.50.0; extra == 'crewai'
30
+ Provides-Extra: dev
31
+ Requires-Dist: mypy>=1.10; extra == 'dev'
32
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
33
+ Requires-Dist: pytest>=8.0; extra == 'dev'
34
+ Requires-Dist: respx>=0.21; extra == 'dev'
35
+ Requires-Dist: ruff>=0.4; extra == 'dev'
36
+ Provides-Extra: langchain
37
+ Requires-Dist: langchain-core>=0.1.0; extra == 'langchain'
38
+ Provides-Extra: openai-agents
39
+ Requires-Dist: openai-agents>=0.1.0; extra == 'openai-agents'
40
+ Provides-Extra: websocket
41
+ Requires-Dist: python-socketio[asyncio-client]<6.0,>=5.10; extra == 'websocket'
42
+ Description-Content-Type: text/markdown
43
+
44
+ # axonpush
45
+
46
+ Python SDK for [AxonPush](https://axonpush.com) — real-time event infrastructure for AI agent systems.
47
+
48
+ Publish, subscribe, trace, and deliver agent events with sub-100ms latency. Drop-in integrations for LangChain, OpenAI Agents SDK, Claude/Anthropic, and CrewAI.
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ pip install axonpush
54
+ ```
55
+
56
+ With framework integrations:
57
+
58
+ ```bash
59
+ pip install axonpush[langchain] # LangChain/LangGraph
60
+ pip install axonpush[openai-agents] # OpenAI Agents SDK
61
+ pip install axonpush[anthropic] # Claude/Anthropic
62
+ pip install axonpush[crewai] # CrewAI
63
+ pip install axonpush[all] # Everything
64
+ ```
65
+
66
+ ## Quick Start
67
+
68
+ ```python
69
+ from axonpush import AxonPush, EventType
70
+
71
+ with AxonPush(api_key="ak_...", tenant_id="1") as client:
72
+ # Publish an event
73
+ event = client.events.publish(
74
+ "web_search",
75
+ {"query": "AI agent frameworks"},
76
+ channel_id=1,
77
+ agent_id="researcher",
78
+ trace_id="tr_run_42",
79
+ event_type=EventType.AGENT_TOOL_CALL_START,
80
+ )
81
+
82
+ # List events
83
+ events = client.events.list(channel_id=1)
84
+
85
+ # Get a trace summary
86
+ summary = client.traces.get_summary("tr_run_42")
87
+ ```
88
+
89
+ ### Async
90
+
91
+ ```python
92
+ from axonpush import AsyncAxonPush
93
+
94
+ async with AsyncAxonPush(api_key="ak_...", tenant_id="1") as client:
95
+ event = await client.events.publish(
96
+ "web_search",
97
+ {"query": "AI agents"},
98
+ channel_id=1,
99
+ agent_id="researcher",
100
+ event_type="agent.tool_call.start",
101
+ )
102
+ ```
103
+
104
+ ## Framework Integrations
105
+
106
+ ### LangChain / LangGraph
107
+
108
+ ```python
109
+ from axonpush import AxonPush
110
+ from axonpush.integrations.langchain import AxonPushCallbackHandler
111
+
112
+ client = AxonPush(api_key="ak_...", tenant_id="1")
113
+ handler = AxonPushCallbackHandler(client, channel_id=1, agent_id="my-agent")
114
+
115
+ # All chain/tool/LLM events are published automatically
116
+ chain.invoke({"input": "..."}, config={"callbacks": [handler]})
117
+ ```
118
+
119
+ ### OpenAI Agents SDK
120
+
121
+ ```python
122
+ from axonpush import AsyncAxonPush
123
+ from axonpush.integrations.openai_agents import AxonPushRunHooks
124
+
125
+ client = AsyncAxonPush(api_key="ak_...", tenant_id="1")
126
+ hooks = AxonPushRunHooks(client, channel_id=1)
127
+
128
+ result = await Runner.run(agent, input="...", hooks=hooks)
129
+ ```
130
+
131
+ ### Claude / Anthropic
132
+
133
+ ```python
134
+ from axonpush import AxonPush
135
+ from axonpush.integrations.anthropic import AxonPushAnthropicTracer
136
+
137
+ client = AxonPush(api_key="ak_...", tenant_id="1")
138
+ tracer = AxonPushAnthropicTracer(client, channel_id=1)
139
+
140
+ # Wraps messages.create() — auto-emits events for tool_use, text, turns
141
+ response = tracer.create_message(
142
+ anthropic_client,
143
+ model="claude-sonnet-4-20250514",
144
+ messages=[{"role": "user", "content": "Hello"}],
145
+ )
146
+ ```
147
+
148
+ ### CrewAI
149
+
150
+ ```python
151
+ from axonpush import AxonPush
152
+ from axonpush.integrations.crewai import AxonPushCrewCallbacks
153
+
154
+ client = AxonPush(api_key="ak_...", tenant_id="1")
155
+ callbacks = AxonPushCrewCallbacks(client, channel_id=1)
156
+
157
+ callbacks.on_crew_start()
158
+ result = Crew(
159
+ agents=[...],
160
+ tasks=[...],
161
+ step_callback=callbacks.on_step,
162
+ task_callback=callbacks.on_task_complete,
163
+ ).kickoff()
164
+ callbacks.on_crew_end(result)
165
+ ```
166
+
167
+ ## Real-Time Subscriptions
168
+
169
+ ### SSE
170
+
171
+ ```python
172
+ from axonpush.realtime.sse import SSESubscription
173
+
174
+ with SSESubscription(
175
+ client._transport, channel_id=1, event_type="agent.error"
176
+ ) as sub:
177
+ for event in sub:
178
+ print(f"[{event.agent_id}] {event.identifier}: {event.payload}")
179
+ ```
180
+
181
+ ### WebSocket
182
+
183
+ ```python
184
+ ws = client.connect_websocket()
185
+ ws.on_event(lambda e: print(e.agent_id, e.payload))
186
+ ws.subscribe(channel_id=1, event_type="agent.tool_call.start")
187
+ ws.wait()
188
+ ```
189
+
190
+ ## Resources
191
+
192
+ The client exposes Stripe-style resource objects:
193
+
194
+ | Resource | Methods |
195
+ |---|---|
196
+ | `client.events` | `publish()`, `list()` |
197
+ | `client.channels` | `create()`, `get()`, `update()`, `delete()`, `subscribe_sse()` |
198
+ | `client.apps` | `create()`, `get()`, `list()`, `update()`, `delete()` |
199
+ | `client.webhooks` | `create_endpoint()`, `list_endpoints()`, `delete_endpoint()`, `get_deliveries()` |
200
+ | `client.traces` | `list()`, `get_events()`, `get_summary()` |
201
+
202
+ ## License
203
+
204
+ MIT
@@ -0,0 +1,161 @@
1
+ # axonpush
2
+
3
+ Python SDK for [AxonPush](https://axonpush.com) — real-time event infrastructure for AI agent systems.
4
+
5
+ Publish, subscribe, trace, and deliver agent events with sub-100ms latency. Drop-in integrations for LangChain, OpenAI Agents SDK, Claude/Anthropic, and CrewAI.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install axonpush
11
+ ```
12
+
13
+ With framework integrations:
14
+
15
+ ```bash
16
+ pip install axonpush[langchain] # LangChain/LangGraph
17
+ pip install axonpush[openai-agents] # OpenAI Agents SDK
18
+ pip install axonpush[anthropic] # Claude/Anthropic
19
+ pip install axonpush[crewai] # CrewAI
20
+ pip install axonpush[all] # Everything
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```python
26
+ from axonpush import AxonPush, EventType
27
+
28
+ with AxonPush(api_key="ak_...", tenant_id="1") as client:
29
+ # Publish an event
30
+ event = client.events.publish(
31
+ "web_search",
32
+ {"query": "AI agent frameworks"},
33
+ channel_id=1,
34
+ agent_id="researcher",
35
+ trace_id="tr_run_42",
36
+ event_type=EventType.AGENT_TOOL_CALL_START,
37
+ )
38
+
39
+ # List events
40
+ events = client.events.list(channel_id=1)
41
+
42
+ # Get a trace summary
43
+ summary = client.traces.get_summary("tr_run_42")
44
+ ```
45
+
46
+ ### Async
47
+
48
+ ```python
49
+ from axonpush import AsyncAxonPush
50
+
51
+ async with AsyncAxonPush(api_key="ak_...", tenant_id="1") as client:
52
+ event = await client.events.publish(
53
+ "web_search",
54
+ {"query": "AI agents"},
55
+ channel_id=1,
56
+ agent_id="researcher",
57
+ event_type="agent.tool_call.start",
58
+ )
59
+ ```
60
+
61
+ ## Framework Integrations
62
+
63
+ ### LangChain / LangGraph
64
+
65
+ ```python
66
+ from axonpush import AxonPush
67
+ from axonpush.integrations.langchain import AxonPushCallbackHandler
68
+
69
+ client = AxonPush(api_key="ak_...", tenant_id="1")
70
+ handler = AxonPushCallbackHandler(client, channel_id=1, agent_id="my-agent")
71
+
72
+ # All chain/tool/LLM events are published automatically
73
+ chain.invoke({"input": "..."}, config={"callbacks": [handler]})
74
+ ```
75
+
76
+ ### OpenAI Agents SDK
77
+
78
+ ```python
79
+ from axonpush import AsyncAxonPush
80
+ from axonpush.integrations.openai_agents import AxonPushRunHooks
81
+
82
+ client = AsyncAxonPush(api_key="ak_...", tenant_id="1")
83
+ hooks = AxonPushRunHooks(client, channel_id=1)
84
+
85
+ result = await Runner.run(agent, input="...", hooks=hooks)
86
+ ```
87
+
88
+ ### Claude / Anthropic
89
+
90
+ ```python
91
+ from axonpush import AxonPush
92
+ from axonpush.integrations.anthropic import AxonPushAnthropicTracer
93
+
94
+ client = AxonPush(api_key="ak_...", tenant_id="1")
95
+ tracer = AxonPushAnthropicTracer(client, channel_id=1)
96
+
97
+ # Wraps messages.create() — auto-emits events for tool_use, text, turns
98
+ response = tracer.create_message(
99
+ anthropic_client,
100
+ model="claude-sonnet-4-20250514",
101
+ messages=[{"role": "user", "content": "Hello"}],
102
+ )
103
+ ```
104
+
105
+ ### CrewAI
106
+
107
+ ```python
108
+ from axonpush import AxonPush
109
+ from axonpush.integrations.crewai import AxonPushCrewCallbacks
110
+
111
+ client = AxonPush(api_key="ak_...", tenant_id="1")
112
+ callbacks = AxonPushCrewCallbacks(client, channel_id=1)
113
+
114
+ callbacks.on_crew_start()
115
+ result = Crew(
116
+ agents=[...],
117
+ tasks=[...],
118
+ step_callback=callbacks.on_step,
119
+ task_callback=callbacks.on_task_complete,
120
+ ).kickoff()
121
+ callbacks.on_crew_end(result)
122
+ ```
123
+
124
+ ## Real-Time Subscriptions
125
+
126
+ ### SSE
127
+
128
+ ```python
129
+ from axonpush.realtime.sse import SSESubscription
130
+
131
+ with SSESubscription(
132
+ client._transport, channel_id=1, event_type="agent.error"
133
+ ) as sub:
134
+ for event in sub:
135
+ print(f"[{event.agent_id}] {event.identifier}: {event.payload}")
136
+ ```
137
+
138
+ ### WebSocket
139
+
140
+ ```python
141
+ ws = client.connect_websocket()
142
+ ws.on_event(lambda e: print(e.agent_id, e.payload))
143
+ ws.subscribe(channel_id=1, event_type="agent.tool_call.start")
144
+ ws.wait()
145
+ ```
146
+
147
+ ## Resources
148
+
149
+ The client exposes Stripe-style resource objects:
150
+
151
+ | Resource | Methods |
152
+ |---|---|
153
+ | `client.events` | `publish()`, `list()` |
154
+ | `client.channels` | `create()`, `get()`, `update()`, `delete()`, `subscribe_sse()` |
155
+ | `client.apps` | `create()`, `get()`, `list()`, `update()`, `delete()` |
156
+ | `client.webhooks` | `create_endpoint()`, `list_endpoints()`, `delete_endpoint()`, `get_deliveries()` |
157
+ | `client.traces` | `list()`, `get_events()`, `get_summary()` |
158
+
159
+ ## License
160
+
161
+ MIT
@@ -0,0 +1,64 @@
1
+ [project]
2
+ name = "axonpush"
3
+ version = "0.1.0"
4
+ description = "Python SDK for AxonPush — real-time event infrastructure for AI agent systems"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ requires-python = ">=3.10"
8
+ authors = [{ name = "AxonPush" }]
9
+ classifiers = [
10
+ "Development Status :: 4 - Beta",
11
+ "Typing :: Typed",
12
+ "Framework :: Pydantic :: 2",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3.10",
15
+ "Programming Language :: Python :: 3.11",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ ]
19
+
20
+ dependencies = [
21
+ "httpx>=0.25.0,<1.0",
22
+ "pydantic>=2.0,<3.0",
23
+ "httpx-sse>=0.4.0,<1.0",
24
+ ]
25
+
26
+ [project.optional-dependencies]
27
+ websocket = ["python-socketio[asyncio_client]>=5.10,<6.0"]
28
+ langchain = ["langchain-core>=0.1.0"]
29
+ openai-agents = ["openai-agents>=0.1.0"]
30
+ anthropic = ["anthropic>=0.30.0"]
31
+ crewai = ["crewai>=0.50.0"]
32
+ all = [
33
+ "axonpush[websocket]",
34
+ "axonpush[langchain]",
35
+ "axonpush[openai-agents]",
36
+ "axonpush[anthropic]",
37
+ "axonpush[crewai]",
38
+ ]
39
+ dev = [
40
+ "pytest>=8.0",
41
+ "pytest-asyncio>=0.23",
42
+ "respx>=0.21",
43
+ "ruff>=0.4",
44
+ "mypy>=1.10",
45
+ ]
46
+
47
+ [build-system]
48
+ requires = ["hatchling"]
49
+ build-backend = "hatchling.build"
50
+
51
+ [tool.hatch.build.targets.wheel]
52
+ packages = ["src/axonpush"]
53
+
54
+ [tool.ruff]
55
+ target-version = "py310"
56
+ line-length = 100
57
+
58
+ [tool.mypy]
59
+ python_version = "3.10"
60
+ strict = true
61
+
62
+ [tool.pytest.ini_options]
63
+ asyncio_mode = "auto"
64
+ testpaths = ["tests"]
@@ -0,0 +1,48 @@
1
+ """AxonPush — Python SDK for real-time event infrastructure for AI agent systems."""
2
+
3
+ from axonpush._tracing import TraceContext, get_or_create_trace
4
+ from axonpush._version import __version__
5
+ from axonpush.client import AsyncAxonPush, AxonPush
6
+ from axonpush.exceptions import (
7
+ AuthenticationError,
8
+ AxonPushError,
9
+ ForbiddenError,
10
+ NotFoundError,
11
+ RateLimitError,
12
+ ServerError,
13
+ ValidationError,
14
+ )
15
+ from axonpush.models.apps import App
16
+ from axonpush.models.channels import Channel
17
+ from axonpush.models.events import Event, EventType
18
+ from axonpush.models.traces import TraceListItem, TraceSummary
19
+ from axonpush.models.webhooks import DeliveryStatus, WebhookDelivery, WebhookEndpoint
20
+
21
+ __all__ = [
22
+ # Clients
23
+ "AxonPush",
24
+ "AsyncAxonPush",
25
+ # Models
26
+ "App",
27
+ "Channel",
28
+ "DeliveryStatus",
29
+ "Event",
30
+ "EventType",
31
+ "TraceListItem",
32
+ "TraceSummary",
33
+ "WebhookDelivery",
34
+ "WebhookEndpoint",
35
+ # Tracing
36
+ "TraceContext",
37
+ "get_or_create_trace",
38
+ # Exceptions
39
+ "AuthenticationError",
40
+ "AxonPushError",
41
+ "ForbiddenError",
42
+ "NotFoundError",
43
+ "RateLimitError",
44
+ "ServerError",
45
+ "ValidationError",
46
+ # Meta
47
+ "__version__",
48
+ ]
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class AuthConfig:
5
+ """Immutable auth configuration. Thread-safe (read-only after construction)."""
6
+
7
+ __slots__ = ("api_key", "tenant_id", "base_url")
8
+
9
+ def __init__(
10
+ self,
11
+ api_key: str,
12
+ tenant_id: str,
13
+ base_url: str = "https://api.axonpush.com",
14
+ ) -> None:
15
+ self.api_key = api_key
16
+ self.tenant_id = tenant_id
17
+ self.base_url = base_url.rstrip("/")
18
+
19
+ def headers(self) -> dict[str, str]:
20
+ return {
21
+ "X-API-Key": self.api_key,
22
+ "x-tenant-id": self.tenant_id,
23
+ "Content-Type": "application/json",
24
+ }
@@ -0,0 +1,121 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import contextmanager
4
+ from typing import Any, Dict, Generator, Iterator, Optional
5
+
6
+ import httpx
7
+ from httpx_sse import EventSource, connect_sse, aconnect_sse
8
+
9
+ from axonpush._auth import AuthConfig
10
+ from axonpush.exceptions import (
11
+ AuthenticationError,
12
+ AxonPushError,
13
+ ForbiddenError,
14
+ NotFoundError,
15
+ RateLimitError,
16
+ ServerError,
17
+ ValidationError,
18
+ )
19
+
20
+ _ERROR_MAP: Dict[int, type] = {
21
+ 400: ValidationError,
22
+ 401: AuthenticationError,
23
+ 403: ForbiddenError,
24
+ 404: NotFoundError,
25
+ 429: RateLimitError,
26
+ }
27
+
28
+
29
+ def _raise_for_status(response: httpx.Response) -> None:
30
+ if response.is_success:
31
+ return
32
+
33
+ status = response.status_code
34
+ try:
35
+ body = response.json()
36
+ message = body.get("message", response.text)
37
+ if isinstance(message, list):
38
+ message = "; ".join(str(m) for m in message)
39
+ except Exception:
40
+ message = response.text or f"HTTP {status}"
41
+
42
+ if status == 429:
43
+ retry_after_raw = response.headers.get("Retry-After")
44
+ retry_after = float(retry_after_raw) if retry_after_raw else None
45
+ raise RateLimitError(str(message), retry_after=retry_after)
46
+
47
+ exc_cls = _ERROR_MAP.get(status)
48
+ if exc_cls is not None:
49
+ raise exc_cls(str(message), status_code=status)
50
+
51
+ if status >= 500:
52
+ raise ServerError(str(message), status_code=status)
53
+
54
+ raise AxonPushError(str(message), status_code=status)
55
+
56
+
57
+ class SyncTransport:
58
+ """Synchronous HTTP transport backed by httpx.Client."""
59
+
60
+ def __init__(self, auth: AuthConfig, timeout: float = 30.0) -> None:
61
+ self._auth = auth
62
+ self._client = httpx.Client(
63
+ base_url=auth.base_url,
64
+ headers=auth.headers(),
65
+ timeout=timeout,
66
+ )
67
+
68
+ def request(
69
+ self,
70
+ method: str,
71
+ path: str,
72
+ *,
73
+ json: Any = None,
74
+ params: Optional[Dict[str, Any]] = None,
75
+ ) -> Any:
76
+ response = self._client.request(method, path, json=json, params=params)
77
+ _raise_for_status(response)
78
+ if not response.content:
79
+ return None
80
+ return response.json()
81
+
82
+ @contextmanager
83
+ def stream_sse(
84
+ self, path: str, params: Optional[Dict[str, Any]] = None
85
+ ) -> Generator[EventSource, None, None]:
86
+ with connect_sse(
87
+ self._client, "GET", path, params=params or {}
88
+ ) as event_source:
89
+ yield event_source
90
+
91
+ def close(self) -> None:
92
+ self._client.close()
93
+
94
+
95
+ class AsyncTransport:
96
+ """Asynchronous HTTP transport backed by httpx.AsyncClient."""
97
+
98
+ def __init__(self, auth: AuthConfig, timeout: float = 30.0) -> None:
99
+ self._auth = auth
100
+ self._client = httpx.AsyncClient(
101
+ base_url=auth.base_url,
102
+ headers=auth.headers(),
103
+ timeout=timeout,
104
+ )
105
+
106
+ async def request(
107
+ self,
108
+ method: str,
109
+ path: str,
110
+ *,
111
+ json: Any = None,
112
+ params: Optional[Dict[str, Any]] = None,
113
+ ) -> Any:
114
+ response = await self._client.request(method, path, json=json, params=params)
115
+ _raise_for_status(response)
116
+ if not response.content:
117
+ return None
118
+ return response.json()
119
+
120
+ async def close(self) -> None:
121
+ await self._client.aclose()